
import java.io.*;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * This class enables a user to feed in a text file with a desired output
 * column width, which is output to the screen wrapped to the specifed output
 * column width.
 */
public class TextWrapper {

    private final String SEPARATORS = ",.;:!?";  // Add else if it is necessary
    private final String EMPTYSEPARATORS = "\\s+";
    private final String SPACE = " ";

    public static void main(String[] args) {
        /* this program requires 2 parameters to run */
        if (args.length >= 2) {
            try {
                TextWrapper textWrapper = new TextWrapper();
                String text = textWrapper.readText(new BufferedReader(new FileReader(args[0])));
                int colWidth = Integer.parseInt(args[1]);
                textWrapper.wrapText(text, colWidth);
            } catch (FileNotFoundException e) {
                System.err.println("Could not find file '" + args[0] + "'");
            } catch (NumberFormatException e) {
                System.err.println("'" + args[1] + "' is not a number");
            } catch (IllegalArgumentException e) {
                System.err.println("Argument error. " + e.getMessage());
            } catch (Exception e) {
                System.err.println("Unknown error. " + e.getMessage());
            }
        } else {
            System.out.println("This program requires 2 parameters to run: "
                    + "a filename (string) and a column width (integer)");
        }
    }

    /**
     * Reads from the BufferedReader and returns the contents as a String.
     * @param br - the BufferedReader to read from.
     * @return the text as a String.
     */
    public String readText(BufferedReader br) {
        StringBuffer sb = new StringBuffer();
        try {
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            br.close();
        } catch (IOException e) {
            System.err.println("An IOException occurred: " + e);
        }
        return sb.toString();
    }

    /**
     * Prints the text to the screen, wrapping each line at the 'width'.
     * @param text - the text to wrap and output.
     * @param width - the column width for outputting the text.
     */
    public void wrapText(String text, int width) {
        /* this method needs to be improved - it currently does not wrap text */
        System.out.println(buildWrappedText(text, width));
    }

    
    /**
     * Checks string for separator.
     * @param text - the text to wrap and output.
     * @return - true/false.
     */
    private boolean isSeparator(String text) {
        return (SEPARATORS.indexOf(text) != -1);
    }

    /**
     * Separates words. Sticks separators to the end of the words.
     * @param text - the text.
     * @return - List with separated words.
     */
    private List<StringBuilder> ExtraSplit(String text) {
        String nextToken;
        StringTokenizer st = new StringTokenizer(text, SEPARATORS, true);
        int count = st.countTokens();
        List<StringBuilder> retList = new LinkedList<StringBuilder>();
        for (int c01 = 0; c01 < count; c01++) {
            if (st.hasMoreTokens()) {
                nextToken = st.nextToken();
                if (isSeparator(nextToken)) {
                    if (retList.size() > 0) {
                        retList.set(retList.size() - 1, retList.get(retList.size() - 1).append(nextToken));
                    } else {
                        retList.add(new StringBuilder(nextToken));
                    }
                } else {
                    retList.add(new StringBuilder(nextToken));
                }
            }
        }
        return retList;
    }


    /**
     * wrapping each line at the 'width'.
     * @param text - the text to wrap and output.
     * @param width - the column width for outputting the text.
     * @return wrapped rows
     */
    private String buildWrappedText(String text, int width) {

        if (width < 1) {
            throw new IllegalArgumentException("width < 1");
        }

        if (text == null) {
            throw new IllegalArgumentException("text is null");
        }

        String[] splitText = text.trim().split(EMPTYSEPARATORS);

        if ((splitText.length == 1) && (splitText[0].isEmpty())) {
            throw new IllegalArgumentException("text is empty");
        }

        List<StringBuilder> normalizedText = new LinkedList<StringBuilder>();
        for (int c01 = 0; c01 < splitText.length; c01++) {
            normalizedText.addAll(ExtraSplit(splitText[c01]));
        }
        // In normalizedText we have evry word alone with
        // sticked separators at the end on the word

        StringBuilder full = new StringBuilder();
        StringBuilder part = new StringBuilder();
        StringBuilder curr = new StringBuilder();

        for (int c02 = 0; c02 < normalizedText.size(); c02++) {
            curr = normalizedText.get(c02);
            next:
            for (;;) {
                if (curr.length() + part.length() < width) {
                    if (part.length() > 0) {
                        part.append(SPACE);
                    }
                    part.append(curr);
                    break;
                } else {
                    if (part.length() > 0) {
                        if (full.length() > 0) {
                            full.append(System.getProperty("line.separator"));
                        }
                        full.append(part);
                        part.delete(0, part.length());
                    }
                    for (;;) {
                        if (curr.length() > width) {
                            if (full.length() > 0) {
                                full.append(System.getProperty("line.separator"));
                            }
                            full.append(curr.substring(0, width));
                            curr.delete(0, width);
                        } else {
                            part.append(curr.substring(0, curr.length()));
                            curr.delete(0, curr.length());
                            break next;
                        }
                    }
                }
            }
        }
        if (part.length() > 0) {
            if (full.length() > 0) {
                full.append(System.getProperty("line.separator"));
                full.append(part);
            }
        }
        return full.toString();
    }
}